package io.lvdaxian.mybatis18.builder.xml;

import io.lvdaxian.mybatis18.builder.BaseBuilder;
import io.lvdaxian.mybatis18.datasource.DataSourceFactory;
import io.lvdaxian.mybatis18.io.Resources;
import io.lvdaxian.mybatis18.mapping.Environment;
import io.lvdaxian.mybatis18.plugin.Interceptor;
import io.lvdaxian.mybatis18.session.Configuration;
import io.lvdaxian.mybatis18.session.LocalCacheScope;
import io.lvdaxian.mybatis18.transaction.TransactionFactory;
import io.lvdaxian.mybatis18.type.TypeAliasRegistry;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Optional;
import java.util.Properties;

/**
 * 此方法是 xml config 构建者
 *
 * @author lihh
 */
public class XMLConfigBuilder extends BaseBuilder {
  
  private final Element root;
  
  public XMLConfigBuilder(Reader reader) {
    // 初期化的时候 实例化 Configuration
    super(new Configuration());
    
    // dom4j 处理 xml
    SAXReader saxReader = new SAXReader();
    try {
      // 解析 入口的reader
      Document document = saxReader.read(new InputSource(reader));
      // 拿到 根标签
      root = document.getRootElement();
    } catch (DocumentException e) {
      throw new RuntimeException(e);
    }
  }
  
  public Configuration parseConfiguration() {
    try {
      // 从这里解析插件
      pluginElement(root.element("plugins"));
      // 解析 settings 标签
      settingsElement(root.element("settings"));
      // 解析 数据源标签
      environmentsElement(root.element("environments"));
      // 从这里开始 解析别名
      typeAliasesElement(root.element("typeAliases"));
      
      // 这里开始 解析总的标签【mappers】
      mapperElement(root.element("mappers"));
    } catch (Exception e) {
      throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
    return configuration;
  }
  
  /**
   * Mybatis 允许你在某一点切入映射语句执行的调度
   * <plugins>
   * <plugin interceptor="cn.bugstack.mybatis.test.plugin.TestPlugin">
   * <property name="test00" value="100"/>
   * <property name="test01" value="100"/>
   * </plugin>
   * </plugins>
   */
  private void pluginElement(Element parent) throws Exception {
    if (parent == null) return;
    // 从这里 拿到 内部元素
    List<Element> elements = parent.elements();
    // 循环 遍历拿到多个元素
    for (Element element : elements) {
      // 拿到拦截器
      String interceptor = element.attributeValue("interceptor");
      // 参数配置
      Properties properties = new Properties();
      List<Element> propertyElementList = element.elements("property");
      for (Element property : propertyElementList) {
        properties.setProperty(property.attributeValue("name"), property.attributeValue("value"));
      }
      // 获取插件实现类并实例化
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
  
  /**
   * <settings>
   * <!--缓存级别：SESSION/STATEMENT-->
   * <setting name="localCacheScope" value="SESSION"/>
   * </settings>
   */
  private void settingsElement(Element context) {
    // 从这里判断元素 是否为null
    if (context == null) return;
    
    // 从这里 拿到 子元素
    List<Element> elements = context.elements();
    Properties props = new Properties();
    for (Element element : elements) {
      // 从这里 设置 name/ value
      props.setProperty(element.attributeValue("name"), element.attributeValue("value"));
    }
    // 这里 设置全局缓存
    // 这里 默认设置为false
    configuration.setCacheEnabled(Boolean.parseBoolean(Optional.ofNullable(props.getProperty("cacheEnabled")).orElse("false")));
    // 拿到缓存的 local scoped
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));
  }
  
  /**
   * 解析 环境标签的方法
   *
   * @param context 解析environments 根标签
   * @author lihh
   */
  private void environmentsElement(Element context) throws InstantiationException, IllegalAccessException {
    // 从这里 获取到默认值, 这是默认激活的的数据源
    String environment = context.attributeValue("default");
    // 筛选 多数据源 从 xml 配置中解析出 environment
    List<Element> environmentList = context.elements("environment");
    // 从配置中 拿到 类型别名注册器
    TypeAliasRegistry typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    
    // 遍历 多个环境配置
    for (Element e : environmentList) {
      String id = e.attributeValue("id");
      
      // 表示 筛选到了 默认设置的环境
      if (!environment.equals(id)) continue;
      
      // 拿到事务工厂
      TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();
      
      // 从 这里拿到数据源 Element标签
      Element dataSourceElement = e.element("dataSource");
      // 数据源工厂, 这里数据源工厂 已经注册到 typeAliasRegistry 别名集合中了
      DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
      // 拿到属性[property] 原则上是可以拿到 多个property
      List<Element> propertyList = dataSourceElement.elements("property");
      
      Properties props = new Properties();
      propertyList.forEach(el -> {
        String name = el.attributeValue("name"), value = el.attributeValue("value");
        
        props.setProperty(name, value);
      });
      // 给 数据源工厂 设置属性
      dataSourceFactory.setProperties(props);
      // 拿到数据源
      DataSource dataSource = dataSourceFactory.getDataSource();
      
      // 构建 数据源环境
      Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);
      
      // 配置文件 设置环境
      configuration.setEnvironment(environmentBuilder.build());
    }
  }
  
  /**
   * 这是解析别名的位置
   *
   * @param aliasElement 别名的 element
   * @author lihh
   */
  private void typeAliasesElement(Element aliasElement) {
    if (null == aliasElement) return;
    
    List<Element> elements = aliasElement.elements("package");
    if (null != elements) {
      for (Element element : elements) {
        String typeAliasPackage = element.attributeValue("name");
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      }
    }
    
    // 这里尝试拿到 typeAliases
    List<Element> typeAlias = aliasElement.elements("typeAlias");
    for (Element element : typeAlias) {
      String alias = element.attributeValue("alias");
      String type = element.attributeValue("type");
      
      try {
        Class<?> clazz = Resources.classForName(type);
        
        // 这是不配置别名的时候
        if (null == alias)
          configuration.getTypeAliasRegistry().registerAlias(clazz);
        else configuration.getTypeAliasRegistry().registerAlias(alias, clazz);
      } catch (Exception e) {
        throw new RuntimeException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
      }
    }
  }
  
  /**
   * 从这里开始 处理 mapper element
   *
   * @param mappers element 元素
   * @author lihh
   */
  /*
   * <mappers>
   *	 <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
   *	 <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
   *	 <mapper resource="org/mybatis/builder/PostMapper.xml"/>
   *
   *   <mapper class="cn.bugstack.mybatis.test.dao.IUserDao"/>
   * </mappers>
   */
  private void mapperElement(Element mappers) throws IOException, DocumentException {
    // 从这里解析单个 mapper
    List<Element> mapperList = mappers.elements("mapper");
    
    for (Element e : mapperList) {
      // 从 mapper 中拿到 resource
      // resource 这个也是缓存的标识
      String resource = e.attributeValue("resource");
      String mapperClass = e.attributeValue("class");
      
      if (null != resource && null == mapperClass) {
        InputStream inputStream = Resources.getResourceAsStream(resource);
        
        // 在for循环里每个mapper都重新new一个XMLMapperBuilder，来解析
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);
        mapperParser.parse();
      } else if (null == resource && null != mapperClass) {
        Class<?> mapperInterface = Resources.classForName(mapperClass);
        configuration.addMapper(mapperInterface);
      }
    }
  }
}
